[Windows] HybridWebView: Route JS evaluation through RunAfterInitialize to preserve WebView2 initialization gate#35702
Closed
ne0rrmatrix wants to merge 219 commits into
Closed
Conversation
…net#34527) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Issue Details: Horizontalspacing / Verticalspacing is not not applied to the first column in GridItemLayout using CollectionView on Android platform. ### Root Cause: The grid spacing was not being distributed symmetrically across the active layout implementations, so edge items did not fully participate when spacing changed at runtime. ### Description of Change: - On Android, the fix in MauiRecyclerView.cs changes how RecyclerView padding is handled for GridItemsLayout. Android was already using SpacingItemDecoration, which applies half-spacing on all four sides of each item. Previously, negative RecyclerView padding canceled that spacing at the control edges. The branch keeps that negative-padding behavior for non-grid layouts, but disables it for GridItemsLayout, allowing the grid’s half-spacing to remain visible at the outer perimeter. This makes the first row and first column visually respond when spacing changes, but it also changes the grid behavior from spacing only between items to spacing around the outside edges as well. **Tested the behavior in the following platforms:** - [x] Android - [x] Windows - [ ] iOS - [ ] Mac ### Reference: N/A ### Issues Fixed: Fixes dotnet#34257 ### Screenshots | Before | After | |---------|--------| | <Video src="https://github.com/user-attachments/assets/578dda69-1d60-474c-a6d8-23b3f9d29a50" Width="300" Height="600"> | <Video src="https://github.com/user-attachments/assets/7f3826e6-5922-4b6f-a6b9-de581b7db6c3" Width="300" Height="600"> |
…otnet#32491) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ## Summary Fixes a critical bug where `HybridWebView.InvokeJavaScriptAsync` would timeout when passing JSON strings as parameters. The issue was caused by improper JavaScript string escaping. The fix extracts business logic into a new `HybridWebViewHelper` class and uses `WebViewHelper.EscapeJsString` for proper escaping. **Fixes**: dotnet#32438 --- ## The Problem Issue dotnet#32438 reported that passing JSON strings to `InvokeJavaScriptAsync` caused timeouts on Windows (required base64 encoding as workaround), while Android worked correctly. ### Root Cause The old `EvaluateJavaScriptAsync` implementation used naive string concatenation: ```csharp // OLD - Breaks when script contains quotes script = "try{eval('" + script + "')}catch(e){'null'};"; ``` When the script contained JSON with quotes, this produced invalid JavaScript: ```javascript try{eval('window.HybridWebView.__InvokeJavaScript(1, 'method', ["{\"userId\":\"value\"}"])')}catch(e){'null'}; ↑ BROKEN - conflicting quotes ``` Result: JavaScript execution failed, causing `InvokeJavaScriptAsync` to timeout. --- ## The Solution ### Core Fix New implementation properly escapes JavaScript strings: ```csharp // NEW - Uses proper escaping var escapedScript = WebViewHelper.EscapeJsString(script); var wrappedScript = $$""" (function() { try { let result = eval('{{escapedScript}}'); // ← Properly escaped return JSON.stringify({ IsError: false, Result: JSON.stringify(result) }); } catch (error) { // ... error handling } })() """; ``` ### Refactoring Extracted ~360 lines from `HybridWebViewHandler` into new `HybridWebViewHelper` class: - `ProcessEvaluateJavaScriptAsync` - Script wrapping with proper escaping - `ProcessInvokeJavaScriptAsync` - JavaScript call building - `ProcessInvokeDotNetAsync` - .NET method invocation from JS - `ProcessRawMessage` - Message routing Handler reduced from ~600 to 244 lines. --- ## Changes **New Files:** - ✨ `HybridWebViewHelper.cs` (470 lines) - Centralized business logic **Modified Files:** - 🔧 `HybridWebViewHandler.cs` - Delegates to helper - 🔧 `HybridWebViewHandler.Standard.cs` - Updated message processing - 🔧 `HybridWebViewHandler.Tizen.cs` - Updated message processing **Tests:** - 🔧 `HybridWebViewTestsBase.cs` - Added 15-second timeout - ✨ Added 5 new tests for JSON parameter scenarios - ✨ Added 3 enhanced quote-handling tests - 🔧 Renamed 20+ tests for clarity - 📝 Corrected error messages to use `InvokeJavaScriptAsync` **Test HTML:** - Added `EchoJsonParameter`, `ParseAndStringifyJson`, `ConcatenateJsonStrings`, `DecodeBase64AndEcho`, `CountJsonArrayItems` --- ## Testing **Platforms**: iOS, Android, Windows, MacCatalyst **Scenarios**: - JSON with quotes and special characters - Complex nested JSON - Multiple JSON parameters - Large arrays (100 items) - Base64 workaround (backward compatibility) --- ## Breaking Changes **None** - Fully backward compatible: - ✅ Existing code works unchanged - ✅ Base64 workaround still works - ✅ No public API changes ### Migration Note If using base64 workaround, you can now simplify: ```csharp // OLD workaround (still works): var json = JsonSerializer.Serialize(obj); var base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(json)); await hybridWebView.InvokeJavaScriptAsync<string>("fn", ..., [base64], ...); // NEW (now works directly): var json = JsonSerializer.Serialize(obj); await hybridWebView.InvokeJavaScriptAsync<string>("fn", ..., [json], ...); ``` --- ## Checklist - [x] Code follows .NET MAUI conventions - [x] 8 new/enhanced tests added - [x] 20+ tests renamed for clarity - [x] No breaking changes - [x] Backward compatible - [x] Error messages corrected - [x] Formatted with `dotnet format` - [x] XML documentation complete - [x] Tested on multiple platforms --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: mattleibow <1096616+mattleibow@users.noreply.github.com>
… on collection update (dotnet#31275) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Root Cause **Windows** Position not updating on item add: CarouselView stayed at the old position after an item was added, leaving current/previous positions unsynced. Cascading events: With `ItemsUpdatingScrollMode.KeepItemsInView`, programmatic smooth scrolls triggered multiple ViewChanged calls, causing `PositionChanged` to fire repeatedly with intermediate values. **Android** Programmatic smooth scrolls produced the same cascading `PositionChanged` events as on Windows. ### Description of Change **Windows** Position Update: On item add, `ItemsView.Position` is explicitly set based on `ItemsUpdatingScrollMode`, keeping current and previous positions in sync. Prevent Cascading Events: Added `_isInternalPositionUpdate`. For collection changes, animations are disabled (animate = false) so scrolling jumps directly, firing `PositionChanged` only once. **Android** Reused the `_isInternalPositionUpdate` logic. Disabled animations during collection changes, ensuring a single clean position update without duplicate events. ### Issues Fixed Fixes dotnet#29529 Tested the behaviour in the following platforms - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Screenshots | Before Issue Fix | After Issue Fix | |------------------|-----------------| | <img width="350" alt="withoutfix" src="https://github.com/user-attachments/assets/d66f0352-a91f-4b85-bb9f-e0e54e55aa5f" /> | <img width="350" alt="withfix" src="https://github.com/user-attachments/assets/d120f268-6954-498d-aab0-42bc3745e296" /> | ---------
…3953) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Issue Details On iOS/MacCatalyst 26+, when the app theme changes (light ↔ dark), the Switch's ThumbColor resets to white instead of keeping the custom color. ### Root Cause On iOS/MacCatalyst 26+, UIKit resets the UISwitch's ThumbTintColor to its default value during theme transitions. ### Description of Change Register for trait collection changes to detect theme switches, then re-apply the custom ThumbColor after UIKit completes its styling. Validated the behavior in the following platforms - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Issues Fixed Fixes dotnet#33783 Fixes dotnet#33767 ### Output ScreenShot |Before|After| |--|--| | <video src="https://github.com/user-attachments/assets/aa595096-4f75-4d7e-b31e-f1f0acc24208" >| <video src="https://github.com/user-attachments/assets/386f9e6e-6df7-40c5-a3e7-59a0bde31df6">|
<!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Issue Details WebView does not scroll when placed inside a ScrollView. The parent ScrollView intercepts vertical touch gestures, preventing the WebView from scrolling its internal content. ### Root Cause The parent ScrollView was intercepting all touch events without checking if the child WebView needed to scroll. This prevented the WebView from receiving touch events and handling its own scrolling. ### Description of Change Added touch event handling to prevent parent interception when WebView scrolls. When touch begins or continues, WebView requests exclusive control from parent. When touch ends or cancels, control returns to parent. This enables WebView scrolling while preserving normal parent-child interaction. Validated the behavior in the following platforms - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Issues Fixed Fixes dotnet#32971 ### Output ScreenShot |Before|After| |--|--| | <video src="https://github.com/user-attachments/assets/12b3ca6e-582d-4a50-9ea1-f49027f2d907" >| <video src="https://github.com/user-attachments/assets/2fbfd03c-4432-49e9-8b2d-6e7643f57487">|
… to null (dotnet#34741) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Issue Details On iOS and macCatalyst, setting BackgroundColor = null on an Entry or Editor at runtime does not restore the native default appearance. Once a custom color is applied, it persists even after the property is cleared. ### Root Cause The shared iOS background update logic only resets BackgroundColor = null for LayoutView subclasses — all other UIView types silently return. MauiTextField (Entry) and MauiTextView (Editor) are not LayoutView subclasses, so the reset is skipped. ### Description of Change Platform-specific MapBackground methods were added to EntryHandler.iOS.cs and EditorHandler.iOS.cs. When Background.IsNullOrEmpty(), they now explicitly set platformView.BackgroundColor = null to restore native appearance. Validated the behavior in the following platforms - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Issues Fixed Fixes dotnet#34611 ### Output ScreenShot |Before|After| |--|--| | <video src="https://github.com/user-attachments/assets/5ca30c6d-c069-4c04-989b-4dae36584cb4" >| <video src="https://github.com/user-attachments/assets/ee9e2a2e-c210-47cc-9f85-2526780d398b">| --------- Co-authored-by: Jakub Florkowski <42434498+kubaflo@users.noreply.github.com>
…t placed child to the Border control in iOS/ Mac platform (dotnet#33330) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Issue Details: Stacklayout is not rendered when clip is applied and StackLayout placed child to the Border control in iOS/ Mac platform. ### Root Cause: When the clip is applied to the StackLayout, its ContainerView (WrapperView) is being inserted at index 0 to its parent control Border, which places it below Border's background layer in the z-order. As a result, the stacklayout is not visible in the view in iOS and Mac platform. ### Description of Change: The wrapper view is brought to the front of its parent’s subview stack so it renders above the Border background in the Z order. **Tested the behavior in the following platforms.** - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Reference: N/A ### Issues Fixed: Fixes dotnet#33241 ### Screenshots | Before | After | |---------|--------| | <img width="369" height="606" alt="image" src="https://github.com/user-attachments/assets/0be8bc27-5de4-41ad-a41f-92581513ac55" /> | <img width="369" height="606" alt="image" src="https://github.com/user-attachments/assets/6a3590f6-2763-473a-aa91-ee1113e48ec3" /> |
…cription is set (dotnet#33979) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Root Cause WinUI `TextBlock` (used by `Label`) automatically exposes its `Text` to UI Automation. `ContentPanel` uses the default `FrameworkElementAutomationPeer`, which exposes both the parent’s `AutomationProperties.Name` and all child elements. Unlike Android (`NoHideDescendants`) or iOS (`AccessibilityElementsHidden`), Windows has no single property to hide descendants while keeping the parent accessible. As a result, both Tab navigation and Browse mode announced duplicate content. ### Description of Change Implemented a custom `ContentPanelAutomationPeer` that overrides three core UI Automation methods (GetAutomationControlTypeCore , `GetLocalizedControlTypeCore`, `GetChildrenCore`) to conditionally modify behavior when Description is present. When a Description exists, the control is exposed as `AutomationControlType.Text` (enables browse mode navigation; alternatives like Custom announce "custom" suffix, Group causes browse mode to skip the element), the "text" announcement suffix is suppressed via empty `GetLocalizedControlTypeCore()` return, and child elements are hidden by returning null from `GetChildrenCore()` to prevent duplication. When no Description is present, default behavior is preserved with `AutomationControlType.Custom` and children remain accessible. The `HasDescription` helper property centralizes the non-empty Description check across all three override methods, ensuring consistent conditional logic following the MAUI `AutomationPeer patterns. ### Issues Fixed Fixes dotnet#33373 ### Platforms Tested - [ ] iOS - [x] Android - [x] Windows - [x] Mac ### Screenshots | Before Fix | After Fix | |------------|-----------| | <video width="350" alt="withoutfix" src="https://github.com/user-attachments/assets/c5f8f114-fbeb-42d1-8601-e75dad57a1a7" /> | <video width="350" alt="withfix" src="https://github.com/user-attachments/assets/d2405df5-64f3-4cd9-8b38-40911ce4fbd6" /> | ---------
…#33459) <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Description of Change <img width="870" height="73" alt="image" src="https://github.com/user-attachments/assets/e27cb50d-db93-431b-831c-24f7faedf9b1" /> Invalidation propagation can be quite consuming especially when switching binding context and multiple properties change (i.e. having multiple labels inside a layout, all of them changing the text). Ti PR avoids useless propagations (same behavior `requestLayout` intrinsically has on Android). # Conflicts: # src/Core/src/Platform/iOS/MauiView.cs
…g lifecycle transition (dotnet#34901) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ## Description Fixes dotnet#34900 Replaces an opaque `NullReferenceException` with an informative `InvalidOperationException` in `ContainerView` when the Android `Context` is no longer available. ### Root Cause `IMauiContext.Context` (the Android `Context`) is stored via a `WeakReference` to the Activity. During lifecycle transitions (e.g., Activity being GC'd), this reference can become null. When `NavigationRootManager.Connect()` calls `view.ToContainerView(mauiContext)`, it creates a `ContainerView` whose base `LinearLayout` constructor receives a null `Context`, causing an NRE deep in the JNI interop layer. ### Fix Added a null-coalescing throw expression in the `ContainerView` constructor: ```csharp public ContainerView(IMauiContext context) : base(context.Context ?? throw new InvalidOperationException( "Unable to create a ContainerView: the Android Context is no longer available. " + "This can occur when the Activity has been collected during a lifecycle transition.")) ``` This provides a clear, actionable error message instead of the cryptic NRE that pointed at `NavigationRootManager.Connect` line 60. ### Stack trace from the original crash ``` [AndroidRuntime] at Microsoft.Maui.Platform.NavigationRootManager.Connect [AndroidRuntime] at Microsoft.Maui.Handlers.WindowHandler.CreateRootViewFromContent [AndroidRuntime] at Microsoft.Maui.Handlers.WindowHandler.MapContent ``` ---------
<!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Root Cause: CarouselView does not utilize the ItemSpacing property during layout styling ### Description of Change Added an item container style based on the horizontal or vertical orientation of the CarouselView, and applied a corresponding style to the ListViewBase <!-- Enter description of the fix in this section --> ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes dotnet#29772 ### Tested the behaviour in the following platforms - [x] Windows - [x] Android - [x] iOS - [x] Mac ### Screenshot | Before Issue Fix | After Issue Fix | |----------|----------| | <img src="https://github.com/user-attachments/assets/4af88696-c5a6-4683-bceb-1933781368f3"> | <img src="https://github.com/user-attachments/assets/f55c2b45-22c3-4ab2-aae5-c668adaf33e4"> | <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. -->
<!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Issue Detail RadioButtonGroup is not functioning correctly when RadioButton controls are placed inside a ContentView in .NET 10. ### Root Cause PR [dotnet#32640](dotnet#32604) (fixing dotnet#32466) removed the coerceValue and Value = this initialiser from RadioButton, so RadioButton.Value now defaults to null. This introduced two distinct bugs in the ContentView+ControlTemplate scenario: - AddRadioButton guard object.Equals(radioButton.Value, this.SelectedValue) evaluates to object.Equals(null, null) == true, causing every button added via a ControlTemplate to be auto-checked immediately. - When the layout is not yet attached to a Page (common during ControlTemplate inflation), GetVisualRoot() returns null. The original fallback radioButton.Parent resolves to the button's immediate parent (Border), which only contains that single button — so other RadioButtons in the same group are never found and never unchecked. ### Description of Change - Updated the equality check in AddRadioButton to ensure comparison only happens when SelectedValue is not null. This avoids unintended selection during initialization. - Improved the root resolution logic in UncheckOtherRadioButtonsInScope. If no visual root is found, fallback to the RadioButtonGroupController’s layout (group container). ### Issue Fixed Fixes dotnet#34759 ### Screenshots | Before | After | |---------|--------| | <video src="https://github.com/user-attachments/assets/39e1e45d-dd04-42b7-bb85-aea83124b0cb"> | <video src="https://github.com/user-attachments/assets/252587b2-10ae-4eb9-968e-de114756aa0c"> | --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
### Description of Change I tried to improve the access time on BindableProperty given they're being accessed a lot during the application usage. As we can see using Array + SIMD is actually performing better when the number of BindableProperty set on a BindableObject is less than ~10, though it becomes worse after that. Array + SIMD also (obviously) allocates less memory. Considering that SIMD may perform differently on different platforms I felt it would be better to simply improve the current Dictionary based implementation by leveraging: - integer keys - CollectionMarshal on GetOrAdd ### Benchmarks | Strategy | PropertiesToSet | Mean | Error | StdDev | Gen0 | Gen1 | Allocated | |----------------------- |---------------- |------------:|---------:|---------:|-------:|-------:|----------:| | Dictionary<BindableProperty> | 1 | 65.22 ns | 0.246 ns | 0.218 ns | 0.0889 | 0.0001 | 744 B | | Array + SIMD | 1 | **52.93 ns** | 0.750 ns | 0.665 ns | 0.0678 | - | 568 B | | Dictionary<int,> | 1 | 59.29 ns | 0.563 ns | 0.440 ns | 0.0889 | 0.0001 | 744 B | | | | Dictionary<BindableProperty> | 3 | 150.70 ns | 0.344 ns | 0.322 ns | 0.1500 | 0.0005 | 1256 B | | Array + SIMD | 3 | **122.09 ns** | 0.163 ns | 0.127 ns | 0.1290 | 0.0002 | 1080 B | | Dictionary<int,> | 3 | 133.50 ns | 0.231 ns | 0.180 ns | 0.1500 | 0.0005 | 1256 B | | | | Dictionary<BindableProperty> | 8 | 426.76 ns | 0.686 ns | 0.641 ns | 0.3662 | 0.0033 | 3064 B | | Array + SIMD | 8 | **318.26 ns** | 0.438 ns | 0.409 ns | 0.3028 | 0.0024 | 2536 B | | Dictionary<int,> | 8 | 338.98 ns | 2.381 ns | 1.988 ns | 0.3662 | 0.0038 | 3064 B | | | | Dictionary<BindableProperty> | 15 | 773.49 ns | 2.593 ns | 2.298 ns | 0.5798 | 0.0095 | 4856 B | | Array + SIMD | 15 | 665.18 ns | 0.846 ns | 0.791 ns | 0.5531 | 0.0076 | 4632 B | | Dictionary<int,> | 15 | **581.04 ns** | 0.431 ns | 0.360 ns | 0.5798 | 0.0095 | 4856 B | | | | Dictionary<BindableProperty> | 30 | 1,542.20 ns | 3.342 ns | 3.126 ns | 1.1692 | 0.0381 | 9784 B | | Array + SIMD | 30 | 1,419.83 ns | 1.301 ns | 1.154 ns | 1.0796 | 0.0324 | 9032 B | | Dictionary<int,> | 30 | **1,165.86 ns** | 1.599 ns | 1.496 ns | 1.1692 | 0.0381 | 9784 B | | | | Dictionary<BindableProperty> | 50 | 2,648.69 ns | 3.759 ns | 2.935 ns | 2.0828 | 0.1183 | 17448 B | | Array + SIMD | 50 | 2,587.54 ns | 3.111 ns | 2.429 ns | 1.8196 | 0.0916 | 15224 B | | Dictionary<int,> | 50 | **1,997.17 ns** | 2.174 ns | 2.033 ns | 2.0828 | 0.1183 | 17448 B |
…otnet#33891) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Root Cause When a password `Entry` field is empty and text is programmatically set, the obfuscation logic calculates a negative insert position `(0 - 4 = -4)`. String. `Insert()` doesn't accept negative indices, causing an `ArgumentOutOfRangeException` crash in windows. ### Description of Change Added `Math.Max(0, ...)` to clamp the insert position to a minimum of 0, preventing negative **values.** ### Issues Fixed Fixes dotnet#33334 ### Platforms Tested - [x] iOS - [x] Android - [x] Windows - [x] Mac ### Screenshots | Before Fix | After Fix | |------------|-----------| | <img width="350" alt="withoutfix" src="https://github.com/user-attachments/assets/43993544-7700-4bff-a04c-d34b999ac962" /> | <img width="350" alt="withfix" src="https://github.com/user-attachments/assets/e7f12e20-1cd8-45e7-b07d-bfe7ebac6248" /> |
…eaving custom state (dotnet#33346) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Root Cause: The issue occurs because platform handlers on iOS and Android do not restore button properties to their default values when VisualState setters are removed. In .NET MAUI, when a control transitions from a custom VisualState back to the normal state, property values are set to null to indicate that defaults should be restored. However, the platform handlers interpreted these null values as instructions to take no action, causing previously applied custom values to remain. ### Fix Description: The fix involves updating button-specific extension methods to ensure properties are correctly restored to their platform default values when property values are null. For iOS, the UpdateBackground extension method now explicitly handles UIButton instances. When the background is null or empty, RemoveBackgroundLayer() is called first to prevent stacking gradient layers, and the background is reset to UIColor.Clear. This restores proper transparency and aligns with the default iOS button appearance. When a valid background is provided, the update continues through the existing view background logic. The UpdateTextColor method was also improved to restore the system default text color correctly when TextColor is null. If the button is attached to a UIWindow, the handler clears explicit overrides using SetTitleColor(null, …) for the Normal, Highlighted, and Disabled states, allowing iOS to fall back to its natural appearance-proxy behavior. It then restores TintColor using window.TintColor rather than a hardcoded system color. This ensures the app’s global tint is respected and prevents clearing appearance settings during the initial render phase. For Android, a MaterialButton-specific UpdateTextColor overload was introduced in MauiMaterialButton. The default Material theme ColorStateList is cached as DefaultTextColors during construction, before any MAUI property mapping occurs. When TextColor becomes null, the cached state list is restored directly, preserving all theme-defined states (normal, disabled, etc.). This approach avoids creating temporary controls and guarantees consistent restoration of the original Material theme colors. ### Issues Fixed Fixes dotnet#19690 ### Tested the behaviour in the following platforms - [x] Mac - [x] Windows - [x] iOS - [x] Android ### Output Screenshot | Platform | Before Fix | After Fix | |----------|----------|----------| | Android | <video src="https://github.com/user-attachments/assets/d96c404f-99b0-4dd2-bfdf-01adf629466e"> | <video src="https://github.com/user-attachments/assets/e85313ef-3155-42d3-a1bc-c1b6ef8587b0"> | |iOS | <video src="https://github.com/user-attachments/assets/416724b2-f8fe-4146-be56-12c3f6808693"> | <video src="https://github.com/user-attachments/assets/962ce83a-de7f-4ca0-8339-c8ce88e99288"> |
…tating the device with dialog open (dotnet#31910) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Issue Details - On Android, when opening a TimePicker and then rotating the device, the picker dialog doesn’t seem to redraw itself to match the new screen dimensions. ### Root Cause of the issue - TimePicker lacks orientation change detection entirely. When the device rotates while the dialog is open, TimePicker has no mechanism to dismiss and re-show the dialog with updated layout, unlike DatePicker which detects orientation changes and refreshes the dialog display. ### Description of Change - Added ConnectHandler and related event subscriptions (ViewAttachedToWindow/ViewDetachedFromWindow) to manage display info changes and cleanup when the view is attached or detached. This helps ensure the time picker dialog responds to device orientation changes and releases resources properly. - Implemented OnMainDisplayInfoChanged to dismiss and recreate the time picker dialog with the current time when the device orientation changes, preserving user selection progress. ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes dotnet#31658 ### Reference - [DatePickerHandler](https://github.com/dotnet/maui/blob/main/src/Core/src/Handlers/DatePicker/DatePickerHandler.Android.cs) ### Tested the behaviour in the following platforms - [ ] - Windows - [x] - Android - [ ] - Mac - [x] - iOS ### Output | Before | After | |----------|----------| | <video src="https://github.com/user-attachments/assets/55091d04-0aa3-4794-aab8-fb5dfc71624e"> | <video src="https://github.com/user-attachments/assets/56c01750-00a1-4c63-8788-ca6c35d7dbd5"> | <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. -->
<!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Issue details Changing the Location property on a Microsoft.Maui.Controls.Maps.Pin does not change the location of the Pin on the map. ### Root cause `MapPinHandler.Android.cs` was only updating the `MarkerOptions` object when the Location property changed The actual `Marker` object already added to the map was not being updated, causing the pin to stay in its original location <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Description of change * **Improved marker association logic**: Updated the `AddPins` method in `MapHandler` to store marker references in the `MapPinHandler` for future property updates, ensuring tighter integration between pins and markers. Validated the behaviour in the following platforms - [x] Android - [ ] Windows - [ ] iOS - [ ] Mac ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes dotnet#12916 <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. --> ### Output | Before| After| |--|--| | <video src="https://github.com/user-attachments/assets/83bedab7-846b-41c1-872e-b6e0d0cd81a4"> | <video src="https://github.com/user-attachments/assets/fd86fd38-6619-4cf9-999a-de9d05b21e17"> | --------- Co-authored-by: Jakub Florkowski <42434498+kubaflo@users.noreply.github.com>
…#34687) ## Description Fixes dotnet#19866 On iOS, tapping the status bar should scroll the topmost `UIScrollView` to the top. This was not working for `CollectionView`, particularly when hosted inside a `Shell`. ## Root Cause iOS disables its scroll-to-top behavior when **multiple** `UIScrollView` instances in the view hierarchy have `scrollsToTop = true`. The Shell flyout's internal `AccessibilityNeutralTableView` (a `UITableView` subclass) defaults `scrollsToTop` to `true`, conflicting with the `CollectionView`'s `UICollectionView`. ## Fix Two changes: 1. **`ItemsViewController2.ViewDidLoad()`** — Explicitly set `CollectionView.ScrollsToTop = true` to opt in to scroll-to-top behavior 2. **`ShellTableViewController.AccessibilityNeutralTableView`** — Set `ScrollsToTop = false` on Shell's flyout table view to prevent the multi-scroll-view conflict This follows the existing codebase pattern where `ScrollsToTop` is explicitly managed (e.g., `ShellSectionRootHeader` and `ContextActionCell` both set it to `false`). ## Validation Verified with a Sandbox app using a grouped `CollectionView` inside a `Shell` with `TabBar`: - **Without fix**: Diagnostic dump shows two scroll views with `scrollsToTop=True` → status bar tap does nothing - **With fix**: Only `MauiCollectionView` has `scrollsToTop=True` → status bar tap scrolls to top correctly --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ht space above the tab bar even if the page title is empty (dotnet#30382) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Root cause The root cause of this issue is that the Windows Shell implementation always reserves space for the header area when Shell.FlyoutBehavior="Flyout" is set, even when pages have no title or header content. The NavigationView control allocates a fixed height above the tab bar for the header region without checking if the current page actually needs this space. This results in an unwanted gap between the flyout toggle area and the tab bar when navigating to pages with empty titles, creating visual inconsistency compared to FlyoutBehavior="Disabled" mode where no header space is reserved for empty content. ### Description of Issue Fix The fix involves adding header visibility logic to the Windows Shell implementation to remove unwanted space above the tab bar in flyout mode. The implementation introduces three key methods in RootNavigationView.cs: UpdateHeaderVisibility() to evaluate header visibility requirements, IsHeaderContentEmpty() to determine if the toolbar contains no title or title view when in flyout mode, and CollapseEmptyHeader() to properly hide empty headers. The toolbar property setter now calls UpdateHeaderVisibility() when the toolbar changes, and ShellView.cs triggers header visibility updates during tab navigation. This ensures empty headers don't occupy unnecessary space in flyout mode while maintaining normal behavior when headers contain content, resolving the spacing issue above the tab bar when switching between pages in Shell applications on Windows. Tested the behavior in the following platforms. - [x] Windows - [x] Mac - [x] iOS - [x] Android ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes dotnet#30254 <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. --> ### Resaved Test snapshots Resaved the below test snapshot because the ContentPage did not have a title. So, resaved the test snapshot based on the fix. 1. ShellFlowDirectionUpdate 2. VerifyFlyoutBackgroundColor 3. VerifyHamburgerIcon 4. Issue23834FlyoutMisbehavior ### Output | Before Issue Fix | After Issue Fix | |----------|----------| | <video width="270" height="600" src="https://github.com/user-attachments/assets/ca496f96-2500-429f-8720-a8adb7925fce"> | <video width="270" height="600" src="https://github.com/user-attachments/assets/1f0dedd2-4808-46bf-98cf-843ca70294ef"> |
…forms (dotnet#30369) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Issue Detail The FlowDirection property is not respected by the TimePicker control. Setting FlowDirection="RightToLeft" has no visual effect on the control across any platform. ### Root Cause iOS: The MapFlowDirection method incorrectly used the concrete TimePickerHandler type instead of the ITimePickerHandler interface. This bypassed platform-specific logic and defaulted to ViewHandler.MapFlowDirection, which lacked the necessary text alignment handling. Android: The FlowDirection property was not correctly mapped to the native implementation, resulting in incorrect or missing text alignment updates. ### Description of Change iOS: Updated the MapFlowDirection method to use the correct handler interface. Implemented alignment logic in TimePickerExtensions.cs using EffectiveFlowDirection to properly align text for both RTL and LTR layouts. Android: Implemented a platform-specific MapFlowDirection method in TimePickerHandler.Android.cs: For 12-hour format, UpdateTextAlignment is applied to align localized "AM/PM" text based on FlowDirection. For 24-hour format, alignment is handled via UpdateFlowDirection, which sets LayoutDirection and TextDirection to align purely numeric content Validated the behaviour in the following platforms - [x] Android - [ ] Windows dotnet#30322 - [x] iOS - [ ] Mac dotnet#30322 ### Issues Fixed: Fixes dotnet#30192 ### Screenshots | Before | After | |---------|--------| | <img src="https://github.com/user-attachments/assets/37c4a442-1011-4c23-bc2e-8743bc11adc7"> | <img src="https://github.com/user-attachments/assets/91b69287-b8eb-4d40-b8a1-0734ef1f89db"> |
…dotnet#26217) ### Root cause The issue arises because the OnNavigationViewSizeChanged method fails to properly reset the layout measurements before arranging the NavigationView. As a result, the NavigationView does not correctly update its layout in response to size changes, causing misalignment or rendering issues in the ScrollView. ### Description of Issue Fix The fix involves updating the OnNavigationViewSizeChanged() method to include a call to InvalidateMeasure() before arranging the NavigationView. This ensures that the layout is accurately recalculated, allowing the ScrollView and other elements within the TabbedPage to be properly measured and arranged during the subsequent layout cycle. This effectively resolves alignment and rendering issues. Additionally, the Arrange() call is retained within the SizeChanged handler to prevent test failures, specifically avoiding timeout issues observed in the ChangingToNewMauiContextDoesntCrash test. This combination ensures stable layout behavior while resolving the clipping and scrolling issues that occur after window resizing. ### Why Tests were not added: **Regarding the test case:** The issue only occurs when resizing the window, so it is not possible to add a test case for the window resizing behavior. Tested the behavior in the following platforms. - [x] Windows - [x] Mac - [x] iOS - [x] Android ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes dotnet#26103 Fixes dotnet#11402 Fixes dotnet#20028 <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. --> ### Resaved Test snapshots Resaved the below-mentioned test snapshot because elements in the TabbedPage were not properly aligned before the fix. The layout changes in OnNavigationViewSizeChanged (adding Arrange() after InvalidateMeasure()) now ensure proper element alignment within the TabbedPage. 1. DefaultSelectedTabTextColorShouldApplyProperly 2. FontImageSourceColorShouldApplyOnTabIcon 3. VerifyTabbedPageMenuItemTextColor 4. DynamicFontImageSourceColorShouldApplyOnTabIcon 5. Issue1323Test 6. TabBarIconsShouldAutoscaleTabbedPage ### Output | Before Issue Fix | After Issue Fix | |----------|----------| | <video width="270" height="600" src="https://github.com/user-attachments/assets/67d34cc9-323c-4a8d-afc6-dbcceb558ee5"> | <video width="270" height="600" src="https://github.com/user-attachments/assets/0ddb5ce5-300a-4088-98f4-f9a3981d6951"> | ---------
<!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Root Cause: The UpdateCharacterSpacing method didn't apply spacing to the individual text blocks in TimePicker ### Description of Change Enhanced the UpdateCharacterSpacing method to apply CharacterSpacing to individual text blocks (HourTextBlock, MinuteTextBlock, and PeriodTextBlock) within the TimePicker. This ensures the property works correctly even when the control is loaded asynchronously. <!-- Enter description of the fix in this section --> ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes dotnet#30199 ### Tested the behaviour in the following platforms - [x] Windows - [x] Android - [x] iOS - [ ] Mac ### Screenshot | Before Issue Fix | After Issue Fix | |----------|----------| | <img width="1217" height="915" alt="beforeFix30199" src="https://github.com/user-attachments/assets/0b955bf7-5e75-48d3-8455-3fe0cf2c0c5b"> | <img width="1261" height="946" alt="Screenshot 2025-07-10 155258" src="https://github.com/user-attachments/assets/5d2d8a34-fb01-4c58-9fef-be3ad10b7cb0"> | <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. --> ---------
…tnet#34383) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Root Cause: HTML text is applied asynchronously in .NET MAUI on iOS, causing the CollectionView cell to render first and then re-measure after the HTML is applied, which leads to visible resizing (scroll jitter). ### Description of Change Improvements to HTML text handling in CollectionView: * Updated `UpdateText` method in `LabelExtensions.cs` to check if the `UILabel` is inside a CV2 cell using the new `IsPlatformLabelInsideCV2Cell` method, allowing synchronous HTML text updates when safe and avoiding unnecessary layout passes. * Added the `IsPlatformLabelInsideCV2Cell` helper method to walk the UIKit superview chain and detect CV2 cells, improving reliability and preventing crashes in CV1 layouts. * Added a reference to `Microsoft.Maui.Controls.Handlers.Items2` to support the new CV2 cell detection logic. <!-- Enter description of the fix in this section --> ### Why Tests were not added: This bug occurs only during live scrolling of CollectionView cells on iOS when a Label with TextType="Html" is rendered. The issue depends on the precise timing of asynchronous HTML text application, which triggers a second layout pass while cells are visible, causing scroll jitter. Automated tests cannot reliably reproduce this because no framework can simulate real-time scrolling and layout timing at the native speed ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes dotnet#33065 ### Tested the behavior in the following platforms - [x] Windows - [x] Android - [x] iOS - [x] Mac | Before Issue Fix | After Issue Fix | |----------|----------| | <video src="https://github.com/user-attachments/assets/1045cbe3-e869-4e08-9562-8032dd9cea88"> | <video src="https://github.com/user-attachments/assets/0c7b3bbb-4917-482f-ac7e-05e343de3ae0"> | <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. -->
…4936) ## Summary - For `Default` and `Fixed` `FlyoutHeaderBehavior`, the flyout scroll view is now positioned below the header instead of overlapping it with a content inset. This prevents items from rendering behind semi-transparent headers when scrolling. - `Scroll` and `CollapseOnScroll` behaviors are unchanged — they still overlap the header so it can scroll away or shrink. - Adds regression test `FlyoutScrollViewDoesNotOverlapHeaderForDefaultAndFixed` and updates `FlyoutHeaderContentAndFooterAllMeasureCorrectly` to match the new layout for Default/Fixed. Fixes dotnet#34925 ## Changes **`ShellFlyoutLayoutManager.cs`** (iOS): - `SetHeaderContentInset()`: For Default/Fixed, sets `ContentInset.Top = 0` since the scroll view frame already starts below the header. - `LayoutContent()`: For Default/Fixed, adds the full header height to the content Y offset instead of just the margin. **`ShellFlyoutTests.cs`**: - New test: `FlyoutScrollViewDoesNotOverlapHeaderForDefaultAndFixed` — verifies the scroll view frame starts at or below the header bottom for Default and Fixed behaviors. - Updated test: `FlyoutHeaderContentAndFooterAllMeasureCorrectly` — adjusts iOS ScrollView expectations so that only Scroll/CollapseOnScroll use content inset; Default/Fixed use frame positioning. ## Test plan - [x] New regression test `FlyoutScrollViewDoesNotOverlapHeaderForDefaultAndFixed` fails without fix, passes with fix - [x] Existing `FlyoutHeaderContentAndFooterAllMeasureCorrectly` test passes with updated expectations - [ ] Manual verification: open flyout with semi-transparent header and 15+ items, scroll down — items should not be visible behind header 🤖 Generated with [Claude Code](https://claude.com/claude-code) ---------
…t#28071) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Issue Details When a Margin value is set for the Path, it renders correctly. However, when the Path is placed inside a StackLayout, it does not render. ### Root Cause While measuring the Path, the size calculation does not account for the Margin value, which leads to incorrect path rendering. ### Description of Change To include the Margin value in size calculations and ensure proper Path rendering. Validated the behavior in the following platforms - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Issues Fixed Fixes dotnet#13801 ### Output ScreenShot | Before | After | |---------|--------| | <img width="501" alt="image (2)" src="https://github.com/user-attachments/assets/7afdacbd-089b-47c2-bad9-216a19618bca" /> | <img width="494" alt="image (3)" src="https://github.com/user-attachments/assets/c64f2eeb-b9c0-4340-beb6-f35fd63f28fc" /> |
…hicsView (dotnet#34557) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Issue Details The FlowDirection property is not functioning as expected when applied to Drawable controls and GraphicsView. When FlowDirection is set to either RightToLeft or LeftToRight, there is no observable change in layout behavior. ### Root Cause ShapeViewHandler had no FlowDirection mapper, and ShapeDrawable.Draw() never applied canvas mirroring. ### Description of Change **Android**: Updated PlatformGraphicsView to mirror its content horizontally when the layout direction is RTL by applying a translation and scale transformation in the Draw method. **iOS**: Modified PlatformGraphicsView to check EffectiveUserInterfaceLayoutDirection and apply a horizontal flip transformation when in RTL mode. **Windows**: Changed PlatformGraphicsView to concatenate a scale and translation transform for RTL flow direction before drawing content. ### Validated the behaviour in the following platforms - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Issues Fixed: Fixes dotnet#34402 ### Screenshots | Before | After | |---------|--------| | <video src="https://github.com/user-attachments/assets/62bec2c7-dbe5-4696-9f78-d2d1b4bcdaec"> | <video src="https://github.com/user-attachments/assets/d143819d-b525-455b-adeb-d589171de61e"> |
…t#34954) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Issue Details - HEIC images picked via PickPhotosAsync are not displayed — the image appears blank, whereas other image formats (e.g., JPEG, PNG) display correctly. ### Root Cause of the issue - PR [34250](dotnet#34250) moved CompletedHandler invocation into the DismissViewController completion callback. This introduced a GC race condition where PhotoPickerPresentationControllerDelegate.Dispose() fires tcs.TrySetResult([]) before the async CompletedHandler finishes HEIC transcoding. - HEIC is particularly affected because NSItemProvider.LoadDataRepresentationAsync transcoding is significantly slower than JPEG/PNG loading, widening the GC window. ### Description of Change **Race condition prevention:** * In `PhotoPickerDelegate.DidFinishPicking`, the `Handler` property of `PhotoPickerPresentationControllerDelegate` is set to `null` before dismissing the picker to avoid a garbage collection race condition that could interfere with the async completion handler, especially during slow operations like HEIC transcoding. <!-- Enter description of the fix in this section --> ### Issues Fixed Fixes dotnet#34953 ### Tested the behaviour in the following platforms - [ ] - Windows - [ ] - Android - [x] - iOS - [ ] - Mac | Before | After | |----------|----------| | <video src="https://github.com/user-attachments/assets/368192e4-57ea-4732-82df-3e3ca386ab35"> | <video src="https://github.com/user-attachments/assets/0e3a5066-e092-4461-9199-1730475307d8"> | <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. -->
…ion on the second programmatic call (dotnet#34982) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Issue Details The issue occurs when calling the SwipeView.Open() method programmatically multiple times on iOS and MacCatalyst platforms. The first call to open swipe items (such as RightItems or BottomItems) works as expected, but the second call throws a System.ArgumentException with the message “An item with the same key has already been added.” This behavior is specific to certain swipe directions that rely on negative offsets (such as right and bottom swipe actions), where the swipe interaction briefly appears and then resets unexpectedly. As a result, internal state inconsistencies lead to a crash during subsequent calls. ### Root Cause The issue occur because of an incorrect use of Math.Abs on the _swipeOffset value inside the ProgrammaticallyOpenSwipeItem()method. This operation removes the negative sign required for specific swipe directions (such as left or up), causing the offset to become invalid during layout validation. Due to this invalid offset, the swipe view resets to a closed state while still retaining previously added entries in the _swipeItems dictionary. When Open() is called again, the same keys are added again to the dictionary, resulting in a duplicate key exception. ### Description of Change The fix involves removing the Math.Abs operation on _swipeOffset to preserve the correct directional value required for swipe behavior, ensuring that the swipe state remains consistent after layout validation. Additionally, a defensive improvement is introduced by clearing the _swipeItems dictionary before repopulating it in the UpdateSwipeItems() method. This prevents duplicate key insertion and ensures that the method behaves safely even if invoked multiple times under unexpected conditions. ### Issues Fixed Fixes dotnet#34917 ### Validated the behaviour in the following platforms - [ ] Windows - [ ] Android - [x] iOS - [x] Mac ### Output | Before | After | |----------|----------| | <video src="https://github.com/user-attachments/assets/55a131cb-6ccc-4776-80d7-90655651565d"> | <video src="https://github.com/user-attachments/assets/5efc010e-04ec-4e4d-8729-d43f41d2d6bf"> |
…ng a NonFlyOutPage (dotnet#34839) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Issue Details: Title of FlyOutPage is not updating anymore after showing a NonFlyOutPage in Android Platform. ### Root Cause: When Window.Page is swapped from a FlyoutPage to a plain ContentPage, the Window.Page setter clears flyoutPage.Parent = null synchronously before propagating the Window=null event through the element hierarchy. This means by the time NavigationPage.OnWindowChanged(null) fires to clean up the toolbar, the check flyoutPage.Parent is IWindow evaluates to false (Parent is already null). As a result, flyoutPage.Toolbar = null never executes — the stale, now-disconnected NavigationPageToolbar remains attached to FlyoutPage.Toolbar. When FlyoutPage is later restored as Window.Page, FindMyToolbar() traverses the ancestors, finds the stale toolbar on FlyoutPage.Toolbar, and returns early — no new connected toolbar is created. Since the stale toolbar's ToolbarTracker subscriptions were severed by Disconnect(), it never receives CurrentPage change notifications, so the title is permanently frozen. ### Description of Change: Remove the flyoutPage.Parent is IWindow && condition from the guard in NavigationPage.OnWindowChanged(null) in NavigationPage.cs. The remaining check flyoutPage.Toolbar == _toolbar is the correct and sufficient invariant — it ensures we only clear the toolbar that this NavigationPage created. The Parent is IWindow guard was redundant in the normal case (when FlyoutPage is the root page, Parent is IWindow), but fatally incorrect during a Window.Page swap because the ordering guarantee it depended on didn't hold. **Tested the behavior in the following platforms:** - [x] Android - [ ] Windows - [x] iOS - [x] Mac ### Reference: N/A ### Issues Fixed: Fixes dotnet#33615 ### Screenshots | Before | After | |---------|--------| | <Video src="https://github.com/user-attachments/assets/00869428-8cb4-43a8-981b-7ac6b018e184" Width="300" Height="600"> | <Video src="https://github.com/user-attachments/assets/81f94d6d-8c25-4d08-a699-4b3db32e76c8" Width="300" Height="600"> |
…otnet#34970) <!-- Please keep the note below for people who find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment whether this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> This pull request addresses the issue where the `DatePicker` control on MacCatalyst did not correctly raise its `Opened` and `Closed` events. The changes implement a more robust mechanism for detecting when the DatePicker is opened and closed, specifically for MacCatalyst, and add new tests to verify this behavior. ### Description of Change : **MacCatalyst DatePicker Event Handling Improvements:** * Added logic to traverse the internal view hierarchy of the compact `UIDatePicker` to find and wire up `UITextField` subviews, allowing detection of when the DatePicker is opened via the `EditingDidBegin` event. A one-shot observer is registered to detect when the picker popover window closes, ensuring the `Closed` event is raised reliably. (`DatePickerHandler.MacCatalyst.cs`) * Implemented cleanup logic to unwire event handlers and remove observers during disconnect, preventing memory leaks and spurious event firing. (`DatePickerHandler.MacCatalyst.cs`) * Removed previous event handler attachments (`EditingDidBegin`/`EditingDidEnd`) from the proxy, as the new mechanism supersedes them. (`DatePickerHandler.MacCatalyst.cs`) [[1]](diffhunk://#diff-2107542f6f788907263db46eab6a80232ed765aa515806945e8f65681c8421d1L105-L113) [[2]](diffhunk://#diff-2107542f6f788907263db46eab6a80232ed765aa515806945e8f65681c8421d1L125-L136) **Testing Enhancements:** * Added a new test case page (`Issue34848`) and a corresponding UI test to verify that the `Opened` and `Closed` events are raised correctly on MacCatalyst and other platforms, using platform-specific logic to close the DatePicker. (`TestCases.HostApp/Issues/Issue34848.cs`, `TestCases.Shared.Tests/Tests/Issues/Issue34848.cs`) [[1]](diffhunk://#diff-652f2cd8a1e252cf8db29bf33034066d08ec5e3b43ec76a77d477824a08a1f44R1-R46) [[2]](diffhunk://#diff-a57ba10bf75c97b2a6f28c075585a1066df8c7c2c31751e3c799177fca6560b4R1-R42) **General Codebase Improvements:** * Minor code cleanup and improved organization in the DatePicker handler for MacCatalyst, including the addition of necessary using directives. (`DatePickerHandler.MacCatalyst.cs`) <!-- Enter description of the fix in this section --> ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes dotnet#34848 ### Tested the behavior in the following platforms - [ ] Windows - [ ] Android - [ ] iOS - [x] Mac | Before Issue Fix | After Issue Fix | |----------|----------| | <video src="https://github.com/user-attachments/assets/73c4686f-30d0-4a57-bb8d-be6b2d4b7cda"> | <video src="https://github.com/user-attachments/assets/65d6f44e-b145-48de-b41a-77b6abefabc4"> | <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. -->
…5543) <!-- Please keep the note below for people who find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment whether this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> This pull request addresses a memory leak issue involving `GradientBrush` subscriptions in the `TabbedPage` renderer/manager. It ensures that when a `TabbedPage` is removed from the window, any event subscriptions to a shared `GradientBrush` are properly cleaned up, preventing the renderer/manager from being kept alive unintentionally. The changes also add tests to verify this behavior. ### Description of Change **Memory leak prevention and event unsubscription:** * The `Dispose` method in `TabbedRenderer.cs` (iOS) and the `SetElement` method in `TabbedPageManager.cs` (Android) now explicitly unsubscribe from the `GradientBrush.InvalidateGradientBrushRequested` event and clear the brush's parent, ensuring no lingering references when the `TabbedPage` is disconnected. [[1]](diffhunk://#diff-9fde794f3d6a007e6182c0353ba2136323fb69141c1d880e49a42f9015274a53R157-R163) [[2]](diffhunk://#diff-c76199129810d626ed8ca8ea05723ccc0f9b73d76d7de83cb353e9cf3a01bbacR132-R138) **Automated tests for leak prevention:** * Added two tests to `TabbedPageTests.cs` that verify the renderer/manager does not leak a shared `GradientBrush` subscription when a `TabbedPage` is removed, both when the brush is set directly and when applied via a `Style`. These tests use reflection to check for remaining event subscribers and assert that none remain after GC. * Introduced a helper method using reflection to inspect the invocation list of the `InvalidateGradientBrushRequested` event on a `GradientBrush` instance. * Added the necessary `System.Reflection` import to support the new test utilities. <!-- Enter description of the fix in this section --> ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes dotnet#35469 ### Tested the behavior in the following platforms - [ ] Windows - [x] Android - [x] iOS - [ ] Mac | Android Before Issue Fix | Android After Issue Fix | |----------|----------| | <video src="https://github.com/user-attachments/assets/b421d3bd-be0f-4ec6-8198-fe6d9a43b9f4"> | <video src="https://github.com/user-attachments/assets/5fcc7cc5-078b-4975-abb0-5236f89d17d3"> | | iOS Before Issue Fix | iOS After Issue Fix | |----------|----------| | <video src="https://github.com/user-attachments/assets/9150410d-f53b-4a3c-914c-611744535d75"> | <video src="https://github.com/user-attachments/assets/3aba2204-8272-4bdf-8553-baf92158812d"> | <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. -->
…ions (dotnet#34992) ## Description The `.github/instructions/integration-tests.instructions.md` file contained incorrect build commands: ```bash ./build.sh --target=dotnet ./build.sh --target=dotnet-local-workloads ``` These mix up Arcade build script syntax (`./build.sh`) with Cake target syntax (`--target=X`). Unrecognized arguments are silently passed through to MSBuild as properties via `eng/common/build.sh` (line 200-201) and do nothing useful. ## Fix Replace with `dotnet cake` ```bash dotnet cake --target=dotnet dotnet cake --target=dotnet-local-workloads ``` ## Impact Documentation-only change. No functional code changes. AI now uses the right commands. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…and Dropdown Items (dotnet#30612) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Issue Details - On Windows, setting the CharacterSpacing property on a Picker control has no visual effect — the spacing is not applied to either the title text or the individual items in the dropdown. ### Root Cause - On Windows, the Picker control is rendered using a native ComboBox, which does not automatically apply the CharacterSpacing property to the title or the items in the dropdown list. - Additionally, applying CharacterSpacing directly to the ComboBox control does not affect the internal TextBlock used to render the selected item or the item templates. ### Description of Change - Added a new CharacterSpacingConverter class to convert CharacterSpacing values to the correct format for UWP rendering. - Enhanced the UpdateCharacterSpacing method to apply character spacing to the selected item in the ComboBox, including handling scenarios where the control is not yet loaded. - Updated the ComboBoxHeader template to use the new CharacterSpacingConverter for binding character spacing values, and registered the converter as a static resource for use in XAML. ### Issues Fixed Fixes dotnet#30464 ### Validated the behaviour in the following platforms - [x] Windows - [ ] Android - [ ] iOS - [ ] Mac ### Output | Before | After | |----------|----------| | <video src="https://github.com/user-attachments/assets/fb173981-30a9-4bef-804b-e1d51431dcd3"> | <video src="https://github.com/user-attachments/assets/3d1c1ac4-bf52-4fed-9f70-0028b08631cc"> |
…perties.Hint or TapGestureRecognizer (dotnet#35590) > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Issue Details On iOS and MacCatalyst, when a container layout (for example, VerticalStackLayout) has SemanticProperties.Hint set, with or without a TapGestureRecognizer, VoiceOver focuses the entire layout as a single accessibility element but reads only the Hint, ignoring all child label text. On Android, TalkBack handles the same layout correctly by reading the child text followed by the hint. ### Root Cause iOS accessibility uses a flat accessibility model. A UIView can either behave as: - a leaf accessibility element, where VoiceOver reads the view’s AccessibilityLabel and AccessibilityHint while hiding its children, or - a container, where child elements are read individually but the container’s own label and hint are ignored. It cannot behave as both at the same time. The previous MAUI implementation in SemanticExtensions.UpdateSemantics marked layouts with Hint or Description as IsAccessibilityElement = true and assigned AccessibilityHint = Hint, but never populated AccessibilityLabel. As a result, VoiceOver read only the hint, while all child content was hidden because the layout became a leaf accessibility element. **Why Android is unaffected** Android accessibility uses a hierarchical accessibility model. TalkBack walks the native view tree and reads both parent and child accessibility content together. For example, on a VerticalStackLayout with Hint = "more info" containing two labels, TalkBack naturally announces: "[label1], [label2], more info". There is no parent/child exclusivity like on iOS. The MAUI Android implementation sets accessibility properties such as ContentDescription and ImportantForAccessibility per view, and TalkBack correctly combines them while traversing the tree. ### Description of Change **SemanticExtensions.cs:** For ILayout views with Hint set, the fix now populates AccessibilityLabel before promoting the layout to an accessibility element. If Description is provided, it is used directly as the label. Otherwise, the label is synthesized from child IText content. This allows VoiceOver to read both the layout content and the hint together as a single focus unit. The fix also resets IsAccessibilityElement back to false when both Hint and Description are later cleared from the layout. **GesturePlatformManager.iOS.cs:** The existing ShouldGroupAccessibilityChildren value is now captured before modification and restored during cleanup. Also added an AccessibilityActivateCallback registration on MauiView to improve MacCatalyst Ctrl+Option+Space tap reliability, since UIKit’s default activation path is unreliable on Catalyst. **MauiView.cs:** Added AccessibilityActivateCallback : Func<bool>? and overrode AccessibilityActivate() so the callback can invoke TapGestureRecognizer.SendTapped() directly. ### Issues Fixed Fixes dotnet#34380 ### Screenshots **iOS:** | Before Issue Fix | After Issue Fix | |----------|----------| | <video width="300" height="600" src="https://github.com/user-attachments/assets/3e45feaa-ef89-4e20-b29b-20618ea7770e"> | <video width="300" height="600" src="https://github.com/user-attachments/assets/092db6da-9ce6-48d1-a8b3-902f403e8c75"> | **Mac:** | Before Issue Fix | After Issue Fix | |----------|----------| | <video width="300" height="600" src="https://github.com/user-attachments/assets/43f4e5bc-82a6-40e7-89c6-542f5a246fc2"> | <video width="300" height="600" src="https://github.com/user-attachments/assets/13c213c5-22d1-4bfe-8252-a6a015e358fc"> |
…in test projects (dotnet#29905) Also fixes an issue uncovered by the new version where the cake script previously launched packaged DeviceTests apps via`Start-Process shell:AppsFolder\<PFN>!App` and then polled for the result XML's existence. That launcher process exits immediately while the actual app keeps running, so cake had no signal for "app done" — it had to fall back to FileExists, which fires the moment xharness creates the (still-open) results file at byte zero in the new version. Replace this with `IApplicationActivationManager::ActivateApplication`, which returns the real PID of the launched packaged app. The cake task then blocks on `Process.WaitForExit`, guaranteeing the results file handle is closed and the file is complete before we move on. This removes the polling loop, kills the file-handle race against `Application.Current.Exit()`, and matches what XHarness / WinAppDriver do internally. ---------
### Description of Change Fixes `Picker.SelectedIndex` initialization when `SelectedIndex` is set before picker items are available. The requested index is now preserved while the picker has no items, the public `SelectedIndex` remains coerced to `-1` until a valid item list exists, and the pending value is applied once items are loaded. Deferred initialization updates `SelectedItem` but does not raise `SelectedIndexChanged`. Adds focused core, XAML, and UI regression coverage for `ItemsSource` and inline `Picker.Items` initialization paths. ### Issues Fixed Fixes dotnet#9150 ---------
…solveActivity pre-check (dotnet#35652) <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Description of Change Removes the `PlatformUtils.IsIntentSupported(intent)` pre-check from `Browser.OpenAsync(uri, BrowserLaunchMode.External)` on Android, and instead relies on `ActivityNotFoundException` from `Application.Context.StartActivity` as the authoritative "no handler" signal. **Why the pre-check is wrong** `PlatformUtils.IsIntentSupported` calls `Intent.ResolveActivity(pm)`. On Android 11+ (API 30, package visibility), that call returns `null` in **two distinct cases**: 1. No activity exists that can handle the intent. `StartActivity` would also fail. 2. An activity exists but is **invisible** to the caller because of `<queries>` package visibility filtering. `StartActivity` would still succeed — system intent dispatch is not subject to caller-side visibility, only `query*` APIs are. Today MAUI conflates the two and throws `FeatureNotSupportedException` even in case 2, blocking a launch Android could perform. This breaks the common case of opening a URL whose owner is a **verified App Link** that the caller has not explicitly declared as visible (Instagram, Facebook, Spotify, X, TikTok, Google Maps, etc.). The standard `<queries><intent VIEW + scheme=https></intent></queries>` declaration recommended in the docs grants visibility to **generic browsers** (whose VIEW filter is host-less) but **not** to host-bound App Link owners — per [Android's auto-visibility rules](https://developer.android.com/training/package-visibility/automatic#web-intents): > "If the intent filter includes a `<data>` element that contains a host, then your app is NOT considered to handle a web intent." So even with the documented manifest fix, the App Link owner case stays broken. **The fix** ```csharp try { Application.Context.StartActivity(intent); } catch (ActivityNotFoundException ex) { throw new FeatureNotSupportedException( "No activity found to handle URI: " + nativeUri, ex); } ``` `ActivityNotFoundException` is the only authoritative signal that no activity can actually handle the intent, since only the system dispatcher knows. The public contract (`FeatureNotSupportedException` thrown when no activity is available) is preserved — we just wrap the real Android exception instead of guessing from a visibility-filtered query. This matches @jfversluis's own suggestion on the related issue dotnet#27744: > *"Yeah looks like we check if the intent is supported for a URL. I guess if its http(s) we should just open the browser and not do anything further."* A more conservative variant (skip the pre-check only for `http`/`https`, keep it for custom schemes) is described in the linked issue as Option B; happy to switch if reviewers prefer it. **Testing** Manually verified on a Pixel running Android 14: - Before the change: `Browser.OpenAsync("https://www.instagram.com/instagram/", External)` with Instagram installed throws `FeatureNotSupportedException`. Confirmed `AppsFilter: ... BLOCKED` in logcat and confirmed Instagram is missing from the calling app's `dumpsys package queries` visible list despite a correct `<queries>` `http`/`https` block. - After the change: same call opens the Instagram app directly via App Link routing. - Cross-checked behavior is unchanged for: generic URL (no installed App Link owner) → opens default browser; non-web custom scheme intent with no handler → still throws `FeatureNotSupportedException`, now wrapping the underlying `ActivityNotFoundException`. No existing Android-platform tests for `Browser.OpenAsync` to update (`Browser_Tests.cs` covers only the netstandard reference assembly and URI escaping). Happy to add device-level tests if maintainers want them as a follow-up — the test infra change is larger than this fix. ### Issues Fixed Fixes dotnet#35651 ---------
…zeCollectItems (dotnet#35575) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ## Description Fixes dotnet#35574 In `Microsoft.Maui.Resizetizer.After.targets`, the `ResizetizeCollectItems` target calls `GetMauiItems` on referenced projects but only passes `TargetFramework` — it does NOT propagate `AdditionalProperties` from the `ProjectReference` items. This means any conditional item removal in the referenced project (like stripping `MauiIcon` when `IsTestLibrary=true`) is ignored during Resizetizer's collection, causing duplicate filename errors. ## Changes ### Fix (`Microsoft.Maui.Resizetizer.After.targets`) 1. Added an `Update` step that copies `AdditionalProperties` metadata from matching `ProjectReference` items to `_ResizetizeCollectItemsProject` 2. Appended `%(_ResizetizeCollectItemsProject.AdditionalProperties)` to the `Properties` parameter in the `<MSBuild>` task call ### Tests (`ResizetizerTests.cs`) Two new integration tests validate the fix: - **`AdditionalPropertiesExcludesImage`** — Verifies that setting `ExcludeLibraryImage=true` via `AdditionalProperties` causes the library to conditionally remove its `MauiImage`, preventing it from being collected by the app - **`AdditionalPropertiesPassesColorToLibrary`** — Verifies that a `Color` property override flows through `AdditionalProperties` and is used when processing the library image ---------
<!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! Updates the Windows App SDK (WinAppSDK) package version from `1.8.251106002` to `1.8.260508005`. This replaces dotnet#33850 with the latest listed stable 1.8 servicing build available on NuGet. ## Changes Made - Updated `MicrosoftWindowsAppSDKPackageVersion` in `eng/Versions.props`. ---------
…otnet#35421) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Issue Details: When a custom control overrides ChangeVisualState() and applies the Selected state manually, calling base.ChangeVisualState()after the control is deselected causes the element to remain permanently stuck in the Selected visual state. ### Root Cause: In .NET 10.0.60, a fix for dotnet#29815 modified ChangeVisualState() to check whether the element is currently in the "Selected" VSM state before transitioning to PointerOver or Normal. This check was implemented by reading VisualStateGroup.CurrentState?.Name via IsElementInSelectedState() — creating a circular dependency. During a deselect, CurrentState is still "Selected" (it hasn't been cleared yet when ChangeVisualState() calls IsElementInSelectedState()). So isSelected = true → "Selected" is re-applied → element is stuck. ### Description of change: - VisualElement.IsItemSelected (internal property) — has an equality guard to avoid redundant recomputation and routes through ChangeVisualState() so that state priorities (Disabled > Selected > PointerOver > Normal) are always respected. - VisualStateManager.IsElementInSelectedState() — now simply returns element.IsItemSelected instead of reading CurrentState. ### Validated the behaviour in the following platforms - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Fixes Fixes dotnet#35399 ### Screenshots | Before | After | |---------|--------| | <video src="https://github.com/user-attachments/assets/0fe16315-5561-4b08-92bc-094b673579a9"> | <video src="https://github.com/user-attachments/assets/52a0bc40-04f3-4e1f-b314-9726ae883f08"> |
<!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Description of Change In order to provide a good reference, I repeated the flow twice and collect the results. With the `main`, we do have this memory footprint: <img width="1436" height="138" alt="image" src="https://github.com/user-attachments/assets/f0b57463-7a8f-4767-9f55-301d5696d4bf" /> Those `System.Action` and `DisplayClass` allocations are caused by the usage of `ForEach` method, they fancy but they come with a cost. Since they're inside a loop the compiler couldn't cache them (my theory) which causes a lot of allocations. To remove all those allocations I rewrite the code using the boring `foreach` loop. And you can see the memory footprint below: <img width="1440" height="113" alt="image" src="https://github.com/user-attachments/assets/e9ab701b-29c6-42cc-9e86-906df4cc503e" /> Where the only allocation now is related with the `List` that is created inside the method and I wasn't able to remove on this PR. I'll let it for the future me or contributors. ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes dotnet#35654 <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. --> From IA review: <img width="658" height="198" alt="image" src="https://github.com/user-attachments/assets/44b9be3a-c6ee-4ae9-a5d0-2bb5d9bcd603" /> The 19 → 26 difference is just sampling noise. GCAllocationTick fires every ~100 KB of allocations, not on every individual allocation. The improved run had 2,017 sampled events vs 2,000 in the baseline (17 more), which means a slightly different sampling window was captured. That's enough to shift counts on any type by a few ticks. What actually changed: ✅ Action<Animation> (30 → 0): gone — the ForEach(delegate) no longer creates a delegate on each call ✅ <>c__DisplayClass20_0 (? → 0): the compiler-generated closure class is gone too ➡️ Animation[] (19 → 26): same root cause (new List<Animation> backing array), just sampling variance ---------
…tforms (dotnet#35632) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Issue Details SwipeItem automatically applies a tint/filter to the icon when a background color is set. Because of that, a custom red icon can change to white/black instead of keeping its original color. ### Root Cause Android/iOS applied tint universally to all drawable types instead of only FontImageSource; Windows used synchronous conversion that rasterized font glyphs instead of preserving vector rendering. ### Description of Change Added platform-specific source-type checking: Android/iOS apply tint only to FontImageSource with fallback to item text color; Windows uses async LoadFileIconAsync to load all sources uniformly through image services, preserving font vector quality. ### Behavior change Currently, the icon color is always determined based on the background color, regardless of whether the icon is a PNG, SVG, or FontImageSource. With the changes in this PR, the behavior will be as follows: - If a PNG or SVG image is provided, no color changes will be applied. The image will be displayed using its original colors. - If a FontImageSource is provided with an explicit color, the specified color will be used. - If a FontImageSource is provided without a color, the existing behavior will be retained. |Scenario|Before|After| |--|--|--| | Png image with background color| <img width="718" height="1466" alt="image" src="https://github.com/user-attachments/assets/271c180c-444e-4919-b721-985f0a58f7e0" /> | <img width="718" height="1466" alt="image" src="https://github.com/user-attachments/assets/baf47387-c54a-4332-a6c4-141fb73ab327" />| | Svg image with background color| <img width="718" height="1466" alt="image" src="https://github.com/user-attachments/assets/61bd8b0f-462c-4461-8a18-033e45d497d5" />| <img width="718" height="1466" alt="image" src="https://github.com/user-attachments/assets/d9129899-4560-4450-946f-d820d1f6820e" />| |FontImage with color | <img width="718" height="1466" alt="image" src="https://github.com/user-attachments/assets/2afd4b8a-f276-4c56-92b2-0449301d39e1" />| <img width="718" height="1466" alt="image" src="https://github.com/user-attachments/assets/1991dce7-f767-4616-a2f1-9e7a35e4c3e9" />| | FontImage with without color|<img width="718" height="1466" alt="image" src="https://github.com/user-attachments/assets/0b07e2fa-a3e1-4be9-b0be-7dad53d823c3" /> | <img width="718" height="1466" alt="image" src="https://github.com/user-attachments/assets/ec5da308-87a3-48b0-968a-bb68df4b9686" />| Validated the behavior in the following platforms - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Issues Fixed Fixes dotnet#23074 ### Output ScreenShot |Before|After| |--|--| | <video src="https://github.com/user-attachments/assets/4e335c3a-bcbc-489d-a040-d8f375b82015" >| <video src="https://github.com/user-attachments/assets/60206583-184f-4ac5-8e31-08cfcd7ac512">| ---------
…to maintain the scroll position when the loop value is changed at runtime (dotnet#29527) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Root cause Loop property was not mapped for Android CarouselView, causing loop behavior to not work. Scroll position was also not maintained on loop value change. ### Description of Change Mapped the Loop property, added logic to maintain the current scroll position when the Loop value is updated., and hid scrollbar when loop is true. ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes dotnet#29411 Fixes dotnet#29449 <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. --> Note: Bhavanesh's [PR](dotnet#29453) also addresses the same issue. However, this PR includes additional logic to maintain the current item when the loop value changes and also handles scrollbar visibility based on the loop value. **Tested the behavior in the following platforms.** - [x] Android - [x] Windows - [x] iOS - [x] Mac | Before | After | |---------|--------| | **Android**<br> <video src="https://github.com/user-attachments/assets/95989e6d-fbc3-47d3-8bae-70176fc5c8b1" width="300" height="600"> | **Android**<br> <video src="https://github.com/user-attachments/assets/569b6b6e-f633-45d4-9f81-a212dd29cb40" width="300" height="600"> | --------- Co-authored-by: Vignesh-SF3580 <102575140+Vignesh-SF3580@users.noreply.github.com>
…sGrouped="True" (dotnet#35609) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Issue Details: When a CollectionView has IsGrouped="True", calling ScrollTo(index) should scroll to the item at that position. This works correctly on Android, but on iOS, MacCatalyst, and Windows scrolled not at all. ### Root Cause: - When ScrollTo(index) is called with no explicit group (GroupIndex == -1), both iOS paths fell through to: return NSIndexPath.Create(0, args.Index); // always section 0 - For a grouped CollectionView with multiple sections, any flat index that exceeded the count of section 0 produced an invalid NSIndexPath — UICollectionView.ScrollToItem silently did nothing. - Additionally, IsIndexPathValid in the Items1 handler (ItemsViewHandler.iOS.cs) lacked a null guard. Since ConvertFlatIndexToGroupedIndexPath returns null for out-of-range indices, this could cause a NullReferenceException. ### Description of change: - Added ConvertFlatIndexToGroupedIndexPath to both ItemsViewHandler.iOS.cs and ItemsViewHandler2.iOS.cs : - Activated only when groupable.IsGrouped == true. - Bounds-checks flat index against itemsSource.ItemCount before walking groups - Iterates IItemsViewSource.ItemCountInGroup(section) to resolve the NSIndexPath(section, item). Returns null for out-of-range indices (safely handled by - Also added indexPath is null || guard to IsIndexPathValid (Items1) to NullReferenceException on out-of-range scroll requests. ### Validated the behaviour in the following platforms - [ ] Android - [ ] Windows - [x] iOS - [x] Mac ### Fixes Fixes dotnet#35326 ### Screenshots | Before | After | |---------|--------| | <video src="https://github.com/user-attachments/assets/846e050f-c2fa-4bf1-a952-789c9e8466ea"> | <video src="https://github.com/user-attachments/assets/d379b495-6fbc-47f1-8e57-36fc472a81ed"> |
### Description of Change This PR reduces Android `CollectionView`/`CarouselView` item allocation overhead from SafeArea inset listener attachment. The Android SafeArea implementation currently attaches inset listeners to item views under `IMauiRecyclerView`, even when those item views have default `SafeAreaEdges` behavior. This is expensive during item realization/recycling and materially increases allocations and GC pressure in scroll-heavy CollectionView scenarios. This change gates Android inset listener attachment for descendants of `IMauiRecyclerView` so listeners are only attached when the target platform view backs an `ISafeAreaView2` with explicitly configured `SafeAreaEdges`. The fix also preserves behavior for dynamic SafeArea changes: - default item views do not attach inset listeners; - explicit `SafeAreaEdges` values, including `SafeAreaEdges.None`, remain eligible; - changing from default to explicit refreshes and attaches the listener; - clearing/changing back to default removes the listener and resets applied padding; - existing `AppBarLayout`, `MaterialToolbar`, and `MauiScrollView` exclusions are preserved. Validation completed: - `dotnet test src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj --filter FullyQualifiedName~SafeAreaTests --nologo` - Android device test build and CollectionView SafeArea listener tests - MauiAppGC repro, Release Android, explicit APK install, `Reset -> End -> Start -> End`, 5 repeats: - `10.0.70`: ~270 MB allocated, `65/2/2` GCs - SafeArea gating patch: ~151 MB allocated, `38/0/0` GCs ### Issues Fixed Fixes dotnet#35344 --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Jakub Florkowski <42434498+kubaflo@users.noreply.github.com>
…nt (dotnet#35625) <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Issue details: A regression was introduced by PR #[34521](dotnet#34521) on Android. It set RadioButton accessibility metadata (ClassName, Checkable, Checked) on AccessibilityNodeInfoCompat during TalkBack. The bool setter for Checked no longer exists in AndroidX Core 1.17.x (API changed), causing a MissingMethodException at runtime. This mainly affects .NET 10 MAUI packages that compile against AndroidX Core 1.16.0.3. ### Description of changes: Fully reverts PR #[34521](dotnet#34521). Removes the IRadioButton accessibility block (ClassName, Checkable, Checked) and the cached s_radioButtonClassName field from SemanticExtensions.cs. Reverts RadioButton.UpdateSemantics back to using ContentAsString() and removes the GetSemanticDescriptionFromContent() and TryGetSemanticDescription() helpers. Also removes the two device tests added for issue #[34322](dotnet#34322) and their helper methods. ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes dotnet#35584 <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. --> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Jakub Florkowski <42434498+kubaflo@users.noreply.github.com>
### Description of Change Fixes `DatePicker.Focus()` on MacCatalyst so programmatic focus correctly updates the virtual view state. On MacCatalyst, `UIDatePicker.BecomeFirstResponder()` can return `false`, which prevented the MAUI focus request from completing successfully and left `IsFocused` / `IsOpen` out of sync. This change adds MacCatalyst-specific command handling for `IView.Focus` and `IView.Unfocus`, explicitly synchronizes `IsFocused` and `IsOpen`, and keeps native/user interaction callbacks aligned with the same state. A MacCatalyst-only regression test was added to verify that a `DatePicker` can be focused, unfocused, and focused again successfully. Validation: - Built `Core.DeviceTests` for `net10.0-maccatalyst`. - Ran focused MacCatalyst DatePicker device tests. - Verified the issue repro changes from reproduced to not reproduced with the local fix. - Verified iOS remains not reproduced. ### Issues Fixed Fixes dotnet#5947 ---------
…oid (dotnet#35563) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Issue Details Button TextColor is set at runtime (for example, DarkRed) and then reset to null. Expected behavior: the button text should return to the platform default color. ### Root Cause Setting TextColor applies native foreground/title overrides. When TextColor was reset to null, some handlers did not actively clear those native overrides. ### Description of Change Android : Saved each control’s original TextColors once and reused it when TextColor is null, restoring the real platform default. iOS : Updated iOS button text color handling in [ButtonExtensions.cs](vscode-file://vscode-app/Applications/Visual%20Studio%20Code.app/Contents/Resources/app/out/vs/code/electron-browser/workbench/workbench.html) to clear overridden title colors when TextColor is null. Validated the behavior in the following platforms - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Issues Fixed Fixes dotnet#35513 ### Output ScreenShot Android |Before|After| |--|--| | <video src="https://github.com/user-attachments/assets/9728c36a-5b0d-4061-8190-fc550e65d128" >| <video src="https://github.com/user-attachments/assets/5a3470c6-b663-4ae6-9d88-5222102a455b">| iOS |Before|After| |--|--| | <video src="https://github.com/user-attachments/assets/bfd4f513-9bf0-4f25-8151-621a593c5d1f" >| <video src="https://github.com/user-attachments/assets/faebe1b3-c01a-4906-8a9b-7da581dec527">| ---------
### Issue Details: Placing a BoxView inside a Border results in incorrect sizing. ### Root Cause On Windows, when content inside a Border has the same or larger explicit size than the Border itself, the WinUI AdjustForExplicitSize logic expands the content's measured size back to its requested dimensions, even after the available size has been reduced to account for the border stroke. This causes MeasureContent to return a desired size larger than the Border by StrokeThickness * 2, resulting in the parent layout allocating an oversized layout slot. Consequently, the right and bottom border strokes can be clipped during rendering. ### Description of Change Override MeasureOverride in the Windows ContentPanel to constrain the reported desired size to the Border's explicit Width and Height when both values are set. This ensures the correct desired size is returned to the parent layout, allowing the Border to receive the intended layout slot size while preserving the existing ArrangeOverride and clipping behavior. ### Validated the behaviour in the following platforms - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Issues Fixed: Fixes dotnet#19668 ### Screenshots | Before | After | |---------|--------| | <img src="https://github.com/user-attachments/assets/b81f0982-e3c2-40d9-ab8b-0b1d743fa99a"> | <img src="https://github.com/user-attachments/assets/f45e3ebf-e48c-48dd-9c39-42c5413c9268"> |
### Description of Change Fixes Android touch arbitration for RecyclerView-backed MAUI items controls when nested inside a perpendicular parent scroll container. A horizontal `CarouselView` or `CollectionView` inside a vertical `ScrollView` now releases vertical gestures to the nearest scrollable parent instead of continuing to hold the gesture in the RecyclerView. Own-axis gestures are still handled by the RecyclerView, and existing disabled `ItemsView` behavior plus `CarouselView.IsSwipeEnabled` behavior are preserved. Adds an Android Appium regression page and test for the nested `CarouselView` scenario. The test verifies that vertical scrolling from inside the carousel works before and after a separate horizontal scroll gesture. ### Issues Fixed Fixes dotnet#7814 ---------
…ze to preserve WebView2 initialization gate Before dotnet#32491, InvokeJavaScriptAsync routed through handler.InvokeAsync which eventually reached RunAfterInitialize on Windows. After dotnet#32491, the shared helper called handler.PlatformView.EvaluateJavaScript directly, bypassing the WebView2 initialization check. This fix restores the RunAfterInitialize gate on Windows for both EvaluateJavaScriptAsync and InvokeJavaScriptAsync by using #if WINDOWS guards to route through the handler command mapper instead of direct platform view calls.
Contributor
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 35702Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 35702" |
Contributor
🔍 Skill Validation Results✅ Static Checks PassedSkills checked: 18 | Agents checked: 4 Full validator output⏭️ LLM Evaluation: Skipped
|
Contributor
|
Hey there @@ne0rrmatrix! Thank you so much for your PR! Someone from the team will get assigned to your PR shortly and we'll get it reviewed. |
Contributor
|
Hey there @ne0rrmatrix! Thank you so much for your PR! Someone from the team will get assigned to your PR shortly and we'll get it reviewed. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!
Description of Change
Fixes a regression introduced by PR #32491 where Windows HybridWebView JS invocation bypassed the WebView2 initialization gate.
Root cause: After #32491, HybridWebViewHelper called handler.PlatformView.EvaluateJavaScript(innerRequest) directly in both EvaluateJavaScriptAsync and InvokeJavaScriptAsync, bypassing the existing RunAfterInitialize(...) gate on Windows.
Fix approach: Use #if WINDOWS\ guards to route JS evaluation through the handler command mapper (\handler.InvokeAsync) for the sync path, and through \hybridPlatformWebView.RunAfterInitialize()\ for the async path. This preserves the WebView2 initialization check that ensures \CoreWebView2\ is ready before executing JavaScript.
Key insight: The Windows \MauiHybridWebView\ platform view has a \RunAfterInitialize\ method that gates JS execution until \CoreWebView2\ is initialized. Direct calls to \handler.PlatformView.EvaluateJavaScript\ bypass this gate entirely.
What NOT to Do (for future agents):
Issues Fixed
Fixes #35696